#include "GameManager.h"

#include "GL/glfw.h"

static GameManager * activeManager = NULL;

GameManager::GameManager() 
	: mCurrLevel(NULL),
	  mEventHandler(this),
	  mIsRunning(false),
	  mIsGraphicsInit(false),
	  mIsPhysicsInit(false),
	  mWindowW(800),
	  mWindowH(800),
	  mPhysicsManager(NULL),
	  mDisplayManager(NULL),
	  mEditMode(false){
}

GameManager::~GameManager(){
	if ( mIsGraphicsInit ){
		glfwCloseWindow();
		glfwTerminate();
	}
}

Displayer * GameManager::GetDisplayer() {
	return this->mDisplayManager;
}

void GameManager::SetCurrLevel( Level * inLev )
{
	mCurrLevel = inLev;
}

void GameManager::RegisterEvent( const EventRec & inEvent){
	mEventHandler.HandleEvent(inEvent);
}

int GameManager::Run() {
	Init();
	
	MainLoop();
	
	return EXIT_SUCCESS;
}

//Main game entry loop
void GameManager::MainLoop() {

	//Store the timestep for calling the Update() function.
	this->mUpdateTimeStep = 1.0/60.0;
	
	while (mIsRunning) {
		//Update physics and gameplay components
		Update();
		
		//Graphics
		Display();
	}
}


void GameManager::InitInputCallbacks() {
	//Note to set this static variable FIRST or else
	//call backs will have nothing.

	//Set-up callbacks

	//
	//Keyboard
	//activeManager = this;
	//GLFWcharfun keyFunc = GameManager::GameKeyboardCallback;
	//glfwSetCharCallback(keyFunc);

	//
	//Mouse
	activeManager = this;
	GLFWmouseposfun mouseFunc = GameManager::GameMouseMoveCallback;
	glfwSetMousePosCallback(mouseFunc);

	GLFWmousebuttonfun mouseButtFunc = GameManager::GameMouseButtonCallback;
	glfwSetMouseButtonCallback(mouseButtFunc);
}

//Init everything related to graphics context, physics world, etc.
bool GameManager::Init(){
	//Must initialize GLFW first
	if (!glfwInit() ) {
		return false;
	}
	
	if (!glfwOpenWindow(mWindowW, mWindowH, 8, 8, 8, 8, 24, 8, GLFW_WINDOW)) {
		glfwTerminate();
		return false;
	}

	//Create all the entities and levels.

	if (InitGraphics() == false) {
		mIsGraphicsInit = false;
		return false;
	}
	
	if (InitPhysics() == false){
		mIsPhysicsInit = false;
		return false;
	}

	InitInputCallbacks();
	
	//Initializes all content in the game
	InitGame();
	
	mIsGraphicsInit = true;
	mIsPhysicsInit = true;
	mIsRunning = true;
	
	return true;
}

bool GameManager::InitGraphics(){ 
	Camera * newCam = new Camera(glm::vec2(0, 0), mWindowW, mWindowH, 1);
	this->mDisplayManager = new Displayer();
	mDisplayManager->SetCamera(newCam);
	mDisplayManager->SetClearColor(1.0, 1.0, 1.0, 1.0);
	mDisplayManager->GetCamera()->SetPixelToMeterRatio(50.0);
	
	return true;
}

bool GameManager::InitPhysics(){ 
	
	this->mPhysicsManager = new PhysicsManager( b2Vec2(0.0, -10.0));
	return true; 
}

bool GameManager::InitGame() {
	// XXX All initialize stuff here.
	Level lev;
	lev.mName = "TEST LEVEL";
	lev.mStartPoint = glm::vec2(0.0, 0.0);
	
	if (!InitLevel( &lev ) ) {
		return false;
	}
}

//Will init and add the player to the game manager.
void GameManager::InitPlayer( PlayerGameEntity * inPlayer )
{
	if (mPlayer != NULL){

		for (int i = 0; i < mObjs.size(); ++i){
			if (mObjs[i] == inPlayer){
				mObjs.erase(mObjs.begin() + i);
				break;
			}
		}

		delete mPlayer; //Gets rid of the previous player.
	}

	mPlayer = inPlayer;
	mObjs.push_back(inPlayer);
}

bool GameManager::InitLevel( Level * inLev ){
	//Init cam
	PlayerGameEntityDef pDef;
	pDef.maxInputSpeed = 0.1;
	pDef.startPos = glm::vec2(0,0);


	
	//XXX Hard coded for now
	pDef.pp.mOrigin = b2Vec2(pDef.startPos.x, pDef.startPos.y);
	pDef.pp.mDensity = 1;
	pDef.pp.mFriction = 0.5;
	pDef.pp.mElastic = 0;
	pDef.pp.mIsStatic = false;

	//XXX Weapon
	//Weapons defaults are fine.

	mPlayer = mSpawner.SpawnPlayer( pDef, this);

	glm::vec2 vertList[4] = {   glm::vec2(-3, 3),
								glm::vec2(-3, -3),
								glm::vec2(3, -3), 
								glm::vec2(3, 3) };

	GroundGameEntity * ent = new GroundGameEntity( vertList, 4 , this);
	PhysicsDisplayPrimitiveDef def;
	def.prim = ent->mPhysPrim;

	PhysicsDisplayPrimitive * groundPrim = new PhysicsDisplayPrimitive(&def);

	this->mDisplayManager->AddDisplayPrimitive(groundPrim);

	this->mPhysicsManager->AddPhysicsObject(ent->mPhysPrim);
	
	mObjs.push_back(static_cast<GameEntity*> ( ent ) );
	
	SpriteDisplayPrimitiveDef sDef;
	sDef.worldPos = glm::vec2(0,0);
	sDef.worldWH = glm::vec2(2,2);
	
	//prim->SetSpriteSheetParams(2, 1, 0, 0);
	//Texture for our sprite.
	//XXX Merge texture and sprite into one entity (just through the interface, keep them separated for data purposes).
	const char * testFileName = "test.tga";
	testTex = new Texture();
	if (testTex->LoadFromImageTGA(testFileName) == -1)
	{
		printf("Unable to load &s\n", testFileName);
		assert(false);
	}
	sDef.tex = testTex;
	sDef.doKeep = true;

	//mDisplayManager->AddFromDef(&sDef);

	//XXX Test Item. XXX//

	ItemGameEntityDef itemDef;
	itemDef.spawnPos = glm::vec2(2, 2);
	itemDef.type = ITEM_TYPE_HEALTH;
	itemDef.value = 100;
	itemDef.worldHeight = 1;
	itemDef.worldWidth = 1;
	itemDef.pp.mIsStatic = false;
	//XXX Figure out a way to do this automatically
	itemDef.dispDef.worldPos = itemDef.spawnPos;
	itemDef.dispDef.worldWH = glm::vec2(itemDef.worldHeight, itemDef.worldWidth);
	itemDef.pp.mOrigin.x = itemDef.dispDef.worldPos.x;
	itemDef.pp.mOrigin.y = itemDef.dispDef.worldPos.x;

	ItemGameEntity * newItem = mSpawner.SpawnItem(itemDef, this);
	if (newItem != NULL)
	{
		mDisplayManager->AddDisplayPrimitive(newItem->GetDisplayPrimitive());
		mPhysicsManager->AddPhysicsObject(newItem->GetPhysicsPrimitive());
	}

	mEntityList.push_back(newItem);

	//THIS IS HORRIBLE
	
	itemDef.spawnPos.x -= 0.25; itemDef.spawnPos.y += 1;
	itemDef.pp.mOrigin.x -= 0.25; itemDef.pp.mOrigin.y += 1;
	itemDef.dispDef.worldPos -= 0.25; itemDef.dispDef.worldPos.y += 1;
	itemDef.pp.mDensity += 1;
	itemDef.pp.mFriction = 1;
	itemDef.pp.mElastic = 10;
	ItemGameEntity * newItem2 = mSpawner.SpawnItem(itemDef, this);

	//Allows for debug the physics of the object.
/*	PhysicsDisplayPrimitiveDef newpp;
	newpp.prim = newItem2->GetPhysicsPrimitive();
	PhysicsDisplayPrimitive * itemDebug = new PhysicsDisplayPrimitive(&newpp);
	*/

	if (newItem2 != NULL)
	{
		//mDisplayManager->AddDisplayPrimitive(itemDebug);
		mDisplayManager->AddDisplayPrimitive(newItem2->GetDisplayPrimitive());
		mPhysicsManager->AddPhysicsObject(newItem2->GetPhysicsPrimitive());
	}

	this->mEntityList.push_back(newItem2);

	//Test a projectile
	ProjectileGameEntityDef projDef;
	projDef.worldPos = glm::vec2(-2, 2);
	projDef.speed = 0.1;
	projDef.startTarget = glm::vec2(3, 2);
	projDef.dispDef.worldPos = projDef.worldPos;
	projDef.dispDef.worldWH = projDef.wh;
	projDef.pp.mOrigin.x = projDef.worldPos.x;
	projDef.pp.mOrigin.y = projDef.worldPos.y;

	ProjectileGameEntity * projectile = mSpawner.SpawnProjectile(projDef, this);

	mEntityList.push_back(projectile);

	//XXX Decide how you are going to treat adding to the display manager (pointer, or copy?)
	//XXX AnimatedSprites should have a SpriteSheet class which dictates everything about them.
	//The SpriteSheet class will have one texture, and multiple AnimatedSprites can share the same
	//SpriteSheet, thus sharing the same texture in memory...

	return true;
}

void GameManager::Update() {
	//Gather user input.
	PollUserInput();

	//Check if it is time for a game update yet.
	if (!IsTimeToUpdate()) {
		return;
	}
	
	//Update Physics
	if (mPhysicsManager) {
		mPhysicsManager->Update(mUpdateTimeStep);
	}
	
	//Update Display objects (This is NOT drawing them, just updating states and such).
	mDisplayManager->Update(mUpdateTimeStep);

	//Updates the player.
	this->mPlayer->Update( mUpdateTimeStep );

	for (int i = 0; i < mEntityList.size(); ++i)
	{
		mEntityList[i]->Update( mUpdateTimeStep );
	}

}

void GameManager::Display() {
	if (!IsTimeToDisplay() || !mDisplayManager) {
		return;
	}
	
	mDisplayManager->Display();

	DrawCrossHair(mCurrMouseInfo.mWorldPos.x, mCurrMouseInfo.mWorldPos.y);

	glfwSwapBuffers();
}

bool GameManager::IsTimeToDisplay() {
	return true;
}

bool GameManager::IsTimeToUpdate() {
	//This will be set first.
	double timeSinceLastUpdate = glfwGetTime();
	
	if (timeSinceLastUpdate > mUpdateTimeStep) {
		glfwSetTime(timeSinceLastUpdate - mUpdateTimeStep);
		return true;
	}
	
	return false;
}



int mKeyMap[][2]= {
	{'D', INPUT_MOVE_RIGHT},
	{'A', INPUT_MOVE_LEFT},
	{'P', INPUT_ZOOM_OUT},
	{'O', INPUT_ZOOM_IN} };

int mEditKeyMap[][2] = { { } };

int mMouseMap[][2] = {
	{0, INPUT_SHOOT}
};

void GLFWCALL GameManager::GameKeyboardCallback(int uniKey, int state) {
	unsigned int mapSize = sizeof(mKeyMap)/(sizeof(int)*2);
	for (int i = 0; i < mapSize; ++i) {
		//Checks lowercase and capital
		if ( uniKey == mKeyMap[i][0] ||
			 uniKey == mKeyMap[i][0] + 32) {
			InputEventRec event;
			event.mType = INPUT_EVENT;
			event.mAction = (INPUT_EVENT_ACTION)mKeyMap[i][1];
			if (state == GLFW_PRESS) {
				event.start = true;
			}
			if (state == GLFW_RELEASE) {
				event.start = false;
			}
			activeManager->RegisterEvent(event);
		}
	}
}

void GetWorldPos( Camera * inCam, 
						const glm::ivec2 & rawMouseCoords,
						glm::ivec2 & outScreenPos, 
						glm::vec2 & outWorldPos ) 
{
		if (!inCam) {
			return;
		}

		glm::mat4x4 * mv = inCam->GetGLMMat(MODELVIEW_MATRIX);
		glm::mat4x4 * pm = inCam->GetGLMMat(PROJECTION_MATRIX);

		glm::ivec4 view;

		glGetIntegerv(GL_VIEWPORT, &view[0]);

		outScreenPos = glm::vec2(rawMouseCoords.x, view[3] - rawMouseCoords.y);

		glm::vec3 world = glm::unProject( glm::vec3(outScreenPos.x, outScreenPos.y, 1), *mv, *pm, view);

		outWorldPos = glm::vec2(world.x, world.y);	
}

void GLFWCALL GameManager::GameMouseMoveCallback( int x, int y )
{
	GetWorldPos(activeManager->mDisplayManager->GetCamera(), 
						glm::ivec2(x, y), 
						activeManager->mCurrMouseInfo.mScreenPos, 
						activeManager->mCurrMouseInfo.mWorldPos);	

	printf("WorldPos: %f %f\nScreenPos: %d %d\n", activeManager->mCurrMouseInfo.mWorldPos.x,
		activeManager->mCurrMouseInfo.mWorldPos.y,
		activeManager->mCurrMouseInfo.mScreenPos.x,
		activeManager->mCurrMouseInfo.mScreenPos.y );
}

void GLFWCALL GameManager::GameMouseButtonCallback(int butt, int state ){
	//Move callback handles x, y location
	activeManager->mCurrMouseInfo.currbutt = (MOUSE_BUTTON)butt;

	switch(state) {
	case GLFW_PRESS:
		activeManager->mCurrMouseInfo.isPressed = true;
		break;
	case GLFW_RELEASE:
		activeManager->mCurrMouseInfo.isPressed = false;
		break;
	}

	//Send mouse event
	for (int i = 0; i < sizeof(mMouseMap)/(sizeof(int)*2); ++i) {
		if (mMouseMap[i][0] == butt) {
			InputEventRec event;
			event.mouseInfo = activeManager->mCurrMouseInfo;
			event.mAction = (INPUT_EVENT_ACTION)mMouseMap[i][1];
			event.start = state == GLFW_PRESS ? true : false;
			event.mType = INPUT_EVENT;
			activeManager->RegisterEvent(event);
		}
	}

}

void GameManager::PollUserInput() {
	activeManager = this;
	//Check keyboard state and spawn events with the Event handler.
	//The event manager will make calls to handle the event.
	static std::set<int> pressedKeys;

	//Go through each registered key event
	
	InputEventRec event;
	event.mAction = INPUT_NONE;
	event.mType = INPUT_EVENT;
	

	int mapSize = 0;
	int * keymap;
	
	if (mEditMode) {
		mapSize = sizeof(mEditKeyMap)/(sizeof(int)*2);
		keymap = &(mEditKeyMap[0][0]);
	}
	else {
		mapSize = sizeof(mKeyMap)/(sizeof(int)*2);
		keymap = &(mKeyMap[0][0]);
	}
	
	for (int i = 0; i < mapSize; ++i) {
		int currkey = *(keymap + (i*2));
		if (glfwGetKey( currkey ) ) {
			pressedKeys.insert(currkey);
			GameKeyboardCallback(currkey, GLFW_PRESS);
		}
		else {
			for (std::set<int>::iterator it = pressedKeys.begin(); 
				it != pressedKeys.end();
				++it) {
				if ((*it) == currkey) {
					pressedKeys.erase(it);
					GameKeyboardCallback(currkey, GLFW_RELEASE);
					break;
				}
			}
		}
	}

}

//For events
